這篇是昨天文章的延伸,並加入了 let/const、Block Scope(區塊作用域)等概念一起解說。
這裡有一個範例程式碼,讀者可以先閱讀,總共宣告四個變數,其中有兩個同名的 cat 變數,只是作用域不同。
var dog = 'globalDog';
let cat = 'globalCat';
if (true) {
let cat = 'blockCat';
var bird = 'globalBird';
console.log(dog);
console.log(cat);
}
console.log(cat);
console.log(bird);
閱讀完程式碼後,來一步步分析這段程式碼的執行過程:
第一步產生全域的 Execution Context,並且放在 Call Stack 裡面,程式裡面宣告的變數不論是 var、let、const 都會在 Heap 裡面分配了記憶體位置,因此 let / const 也會有 hoisting 的特性。
用 var 宣告的變數 dog、bird 存在於 Variable Environment,它們的值為 undefined。
而 let 宣告的變數 cat 則存在於 Lexical Environment,此時 cat 尚未賦值,在實際「賦值之前」的這段期間稱為是 Temporal Dead Zone(TDZ) 暫時死區,若此時取用的話會跳出錯誤訊息。
以下段程式來說,func()
內部在 x 被賦值前都屬於 TDZ。
function func() {
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 2;
}
func();
執行階段,全域變數被賦值。
這個階段會發現到同名的 cat 變數同時存在於 Lexical Environment,不過不同的作用域下程式碼底層的運作會分出不同的區域去存放它們,所以就算同名也不會發生衝突。
讀者可以將範例程式碼的 if 判斷式這段程式碼多複製貼上幾次,然後執行程式碼,會發現還是正常執行。
if (true) {
let cat = 'blockCat';
var bird = 'globalBird';
}
將該賦值的變數賦值,之後的 console.log
都取得到值,所以都可以順利印出。
最後來做個嘗試,在印出 dog 變數前我們再多宣告一個同名的變數,所以新增 var dog = 'changeGlobalDog!!!!';
這行。
讀者可以猜猜執行結果會怎樣。
var dog = 'globalDog';
let cat = 'globalCat';
if (true) {
let cat = 'blockCat';
var bird = 'globalBird';
var dog = 'changeGlobalDog!!!!';
console.log(dog);
console.log(cat);
}
console.log(cat);
console.log(bird);
答案是 dog 變數值被後來宣告的變數蓋掉了,而且也沒有報錯,是不是覺得挺不嚴謹的呢?所以現在都盡量使用 let/const 了。
最後這裡提供 JavaScript Visualizer 這個網站,左邊是想執行的程式碼,右邊會呈現出圖形化的執行過程。
讀者可以自行在左側區塊輸入想執行的程式碼,然後點擊左上角的執行按鈕去觀看執行過程,網站內部也有提供一些像 Execution Context、Hoisting、Closures 等範例供參考。
以下是網站截圖
問兩個 console.log 會印出什麼? 解答放在留言區
function sayHi() {
console.log(name);
console.log(age);
var name = 'Lydia';
let age = 21;
}
sayHi();
JavaScript Visualized - Execution Contexts
Understanding Execution Context and Execution Stack in Javascript
JavaScript execution context — from compiling to execution
JavaScript execution context — lexical environment and block scope
About "LexicalEnvironment" and "VariableEnvironment"
Variable Environment vs lexical environment
分別印出 undefined 和 ReferenceError。
var 會有 hoisting,但未賦值之前使用它會有個初始值 undefined,而 let 變數雖然也有 hoisting,但不會賦予一個初始值,所以拋出錯誤。